﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Ionic.Zlib;

namespace ROToolBelt.IO.GRF
{
    public class GrfReader : IDisposable
    {
        private readonly GrfDecoder decoder;
        private readonly GrfBinaryReader reader;

        public GrfReader(Stream grfStream)
        {
            if (grfStream == null)
                throw new ArgumentNullException("grfStream");
            this.reader = new GrfBinaryReader(grfStream);
            this.decoder = new GrfDecoder();
        }

        public FileEntry[] GetFileEntries()
        {
            GrfHeader header = this.ReadHeader();
            List<FileEntry> entries = new List<FileEntry>();
            byte[] udata = this.GetFileTableData(header);

            using (FileEntryReader entryReader = new FileEntryReader(new MemoryStream(udata)))
                for (int i = 0; i < header.NumberEntries; i++)
                    entries.Add(entryReader.ReadFileEntry());

            return entries.ToArray();
        }

        public byte[] GetData(FileEntry entry)
        {
            byte[] cdata, udata;

            this.reader.BaseStream.Seek(entry.Offset, SeekOrigin.Begin);
            cdata = this.reader.ReadBytes(entry.CompressedLengthAlligned);

            if (entry.Flag == 3 || entry.Flag == 5)
            {
                if ((entry.Flag & 0x02) != 0)
                    cdata = decoder.GrfDecodeFull(cdata, entry.Cycle);
                else if ((entry.Flag & 0x04) != 0)
                    cdata = decoder.GrfDecodeHeader(cdata, entry.CompressedLengthAlligned);
            }

            udata = ZlibStream.UncompressBuffer(cdata);
            return udata;
        }

        private GrfHeader ReadHeader()
        {
            GrfHeader header = new GrfHeader();
            this.reader.BaseStream.Seek(0, SeekOrigin.Begin);
            header.Signature = this.reader.ReadString(16);
            header.Crypt = this.reader.ReadBytes(14);
            header.FileTableOffset = this.reader.ReadInt32() + GrfHeader.GRF_HEADER_SIZE;
            header.Num = this.reader.ReadInt32();
            header.NumberEntries = this.reader.ReadInt32() - 7;
            header.Version = (GrfVersion)this.reader.ReadInt32();

            if (header.Num > header.NumberEntries || header.Signature != "Master of Magic\0")
                throw new InvalidOperationException("Invalid header.");
            return header;
        }

        private FileTableHeader ReadFileTableHeader(GrfHeader header)
        {
            FileTableHeader fileTableHeader = new FileTableHeader();
            this.reader.BaseStream.Seek(header.FileTableOffset, SeekOrigin.Begin);
            fileTableHeader.CompressedLength = this.reader.ReadInt32();
            fileTableHeader.UncompressedLength = this.reader.ReadInt32();
            fileTableHeader.TableDataOffset = this.reader.BaseStream.Position;
            return fileTableHeader;
        }

        private byte[] GetFileTableData(GrfHeader header)
        {
            FileTableHeader fileTableHeader = this.ReadFileTableHeader(header);
            if (fileTableHeader == null)
                throw new InvalidOperationException("FileTableHeader is null");
            this.reader.BaseStream.Seek(fileTableHeader.TableDataOffset, SeekOrigin.Begin);

            byte[] cdata = this.reader.ReadBytes(fileTableHeader.CompressedLength);
            byte[] udata = ZlibStream.UncompressBuffer(cdata);

            return udata;
        }

        #region IDisposable Members

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
                this.reader.Dispose();
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion
    }
}
